home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 9675 / 9675.xpi / chrome / content / simpletimer-notify.js < prev    next >
Text File  |  2009-11-17  |  32KB  |  816 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Simple Timer.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * George Bradt.
  18.  
  19.  * Portions created by the Initial Developer are Copyright (C) 2009
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. var SimpleTimerNotify = {
  39.     // For reference, not used.
  40.     notification: {completed: false,
  41.                    recurring: false,
  42.                    timeDisplay: "",
  43.                    description: "",
  44.                    time24hr: "",
  45.                    timeMilli: 0,
  46.                    day: -1,
  47.                    url: ""
  48.                   },
  49.     notifySep: "\x1D", // used to separate nots. in pref.
  50.  
  51.     // Called when loading Notify Me At dialog.
  52.     // Populate the tree.
  53.  
  54.     onLoadNotify: function() {
  55.         var strbundle = document.getElementById("simtim-strings");
  56.  
  57.         var Application = Components.classes["@mozilla.org/fuel/application;1"].
  58.                     getService(Components.interfaces.fuelIApplication);
  59.  
  60.         var notifyTable = [];
  61.  
  62.         if ( Application.storage.has("notifyTable") ) {
  63.             notifyTable = Application.storage.get("notifyTable", "ERR");
  64.  
  65.             if ( notifyTable === "ERR" ) {
  66.                 alert(strbundle.getString("alert.error.loading.notifications"));
  67.                 return;
  68.             }
  69.         }
  70.         else {
  71.             alert(strbundle.getString("alert.error.loading.notifications"));
  72.             return;
  73.         }
  74.  
  75.         var treeChildren = document.getElementById("simtim-treeItems");
  76.  
  77.         // Clear the old tree data.
  78.         this.onDeleteAllNotify();
  79.  
  80.         for ( var i in notifyTable ) {
  81.             // Bypass completed notifications.
  82.             if ( !notifyTable[i].completed ) {
  83.                 this.addTreeRow(treeChildren, notifyTable[i]);
  84.             }
  85.         }
  86.     },
  87.  
  88.     // Called when user clicks Bookmark.
  89.  
  90.     onBookmarkNotify: function() {
  91.         var strbundle = document.getElementById("simtim-strings");
  92.         var url = window.opener.gBrowser.currentURI.spec;
  93.  
  94.         if ( url === "about:blank" ) {
  95.             alert(strbundle.getString("alert.warning.no.url"));
  96.         }
  97.         else {
  98.             document.getElementById("simtim-tboxNotifyUrl").value = url;
  99.         }
  100.     },
  101.  
  102.     // Called when user clicks Recurring checkbox.
  103.     // Enable/disable day menulist.
  104.  
  105.     onClickRecurringNotify: function() {
  106.         document.getElementById("simtim-mlistDow").disabled =
  107.             !document.getElementById("simtim-cboxNotifyRecurring").checked;
  108.     },
  109.  
  110.     // Called when user clicks on Notifications tree.
  111.  
  112.     onClickNotTree: function(event) {
  113.         var strbundle = document.getElementById("simtim-strings");
  114.         var recurring, dow, mlistDow;
  115.         var tree = document.getElementById("simtim-treeNotifications");
  116.  
  117.         // See if tree disabled (ie. edit already in progress).
  118.         if ( tree.disabled ) {
  119.             return;
  120.         }
  121.  
  122.         // Get the row, col and child element at the point of mouse click
  123.         var tbo = tree.treeBoxObject;
  124.         var row = { }, col = { }, child = { };
  125.         tbo.getCellAt(event.clientX, event.clientY, row, col, child);
  126.  
  127.         if ( col.value ) {
  128.             if ( col.value.id !== "simtim-colRecur" &&
  129.                  event.detail === 2 ) {
  130.                 // No monkey business.
  131.                 tree.disabled = true;
  132.  
  133.                 // User double-clicked on a notification to initiate an edit.
  134.                 // Disable the delete and reset buttons.
  135.                 document.getElementById("simtim-btnDel").disabled = true;
  136.                 document.getElementById("simtim-btnDelAll").disabled = true;
  137.                 document.getElementById("simtim-btnReset").disabled = true;
  138.  
  139.                 // Change label and tooltips on Add and Add/OK buttons.
  140.                 document.getElementById("simtim-btnAddOk").label =
  141.                         strbundle.getString("button.label.replace/OK");
  142.                 document.getElementById("simtim-btnAddOk").tooltipText =
  143.                         strbundle.getString("button.tooltip.replace/OK");
  144.                 document.getElementById("simtim-btnAdd").label =
  145.                         strbundle.getString("button.label.replace");
  146.                 document.getElementById("simtim-btnAdd").tooltipText =
  147.                         strbundle.getString("button.tooltip.replace");
  148.  
  149.                 // Move the notification data back to the upper half.
  150.                 document.getElementById("simtim-tboxNotifyDesc").value =
  151.                         tree.view.getCellText(row.value,
  152.                         tree.columns.getNamedColumn("simtim-colDescription"));
  153.  
  154.                 document.getElementById("simtim-tboxNotifyUrl").value =
  155.                         tree.view.getCellText(row.value,
  156.                         tree.columns.getNamedColumn("simtim-colUrl"));
  157.  
  158.                 document.getElementById("simtim-tspinNotifyTime").value =
  159.                         tree.view.getCellValue(row.value,
  160.                         tree.columns.getNamedColumn("simtim-colTime"));
  161.  
  162.                 mlistDow = document.getElementById("simtim-mlistDow");
  163.                 recurring = tree.view.getCellValue(row.value,
  164.                         tree.columns.getNamedColumn("simtim-colRecur"));
  165.  
  166.                 if ( recurring === "true" ) {
  167.                     document.getElementById("simtim-cboxNotifyRecurring").checked = true;
  168.                     dow = tree.view.getCellValue(row.value,
  169.                             tree.columns.getNamedColumn("simtim-colDow"));
  170.                     var dowFound = false;
  171.                     var itemCount = mlistDow.itemCount;
  172.  
  173.                     for ( var i = 0; i < itemCount; i++ ) {
  174.                          if ( mlistDow.getItemAtIndex(i).value === dow ) {
  175.                             mlistDow.selectedIndex = i;
  176.                             dowFound = true;
  177.                             break;
  178.                         }
  179.                     }
  180.  
  181.                     if ( !dowFound ) {
  182.                         alert(strbundle.getString("alert.error.invalid.dow"));
  183.                         mlistDow.selectedIndex = 0;
  184.                     }
  185.                     else {
  186.                         mlistDow.disabled = false;
  187.                     }
  188.                 }
  189.                 else {
  190.                     document.getElementById("simtim-cboxNotifyRecurring").checked = false;
  191.                     mlistDow.disabled = true;
  192.                 }
  193.  
  194.                 // Stash the row in a custom attribute.
  195.                 document.getElementById("simtim-tboxNotifyDesc").
  196.                         setAttribute("updaterow", row.value);
  197.  
  198.                 return;
  199.             }
  200.  
  201.             if ( col.value.id === "simtim-colRecur" ) {
  202.                 // Toggling Recurring also changes Day.
  203.                 // Post-click value.
  204.                 if ( tree.view.getCellValue(row.value, col.value) === "true" ) {
  205.                     tree.view.setCellText(row.value,
  206.                             tree.columns.getNamedColumn("simtim-colDow"),
  207.                             strbundle.getString("display.dow.daily"));
  208.                     tree.view.setCellValue(row.value,
  209.                             tree.columns.getNamedColumn("simtim-colDow"), "7");
  210.                 }
  211.                 else {
  212.                     tree.view.setCellText(row.value,
  213.                             tree.columns.getNamedColumn("simtim-colDow"), "");
  214.                     tree.view.setCellValue(row.value,
  215.                             tree.columns.getNamedColumn("simtim-colDow"), "-1");
  216.                 }
  217.  
  218.                 return;
  219.             }
  220.  
  221.             if ( col.value.id === "simtim-colUrl" &&
  222.                  event.button === 2 ) {
  223.                 // User right-clicked on URL.
  224.                 var url = tree.view.getCellText(row.value, col.value);
  225.  
  226.                 if ( url ) {
  227.                     this.openLink(url);
  228.                 }
  229.             }
  230.         }
  231.     },
  232.  
  233.     // Called when Add/OK button is clicked.
  234.  
  235.     onAddOkNotify: function() {
  236.         if ( this.onAddNotify() ) {
  237.             this.onOKNotify();
  238.             window.close();
  239.         }
  240.     },
  241.  
  242.     // Called when Add button is clicked.
  243.  
  244.     onAddNotify: function() {
  245.         // The timepicker element always stores its value as a 24 hr clock.
  246.         // For display, convert value to locale format, and remove seconds.
  247.         var strbundle = document.getElementById("simtim-strings");
  248.         var time24hr = document.getElementById("simtim-tspinNotifyTime").value;
  249.         var timeDisplay = new Date("December 23, 2007 " + time24hr).
  250.                     toLocaleFormat("%X").replace(/:\d\d(?!.*:\d\d)/, "");
  251.         var tree = document.getElementById("simtim-treeNotifications");
  252.         var boxobject = tree.boxObject;
  253.         boxobject.QueryInterface(Components.interfaces.nsITreeBoxObject);
  254.         var treeChildren = document.getElementById("simtim-treeItems");
  255.         var treeItems, row = null;
  256.         var timeNode, dayNode = null;
  257.         var dowValue;
  258.         var descr = document.getElementById("simtim-tboxNotifyDesc").value;
  259.         var recurring = document.getElementById("simtim-cboxNotifyRecurring").checked;
  260.         var updaterow = document.getElementById('simtim-tboxNotifyDesc').
  261.                 getAttribute("updaterow");
  262.  
  263.         if ( updaterow ) {
  264.             tree.disabled = false;
  265.  
  266.             // Updating a notification, enable all buttons.
  267.             document.getElementById("simtim-btnDel").disabled = false;
  268.             document.getElementById("simtim-btnDelAll").disabled = false;
  269.             document.getElementById("simtim-btnReset").disabled = false;
  270.  
  271.             // Reset label and tooltip on Add and Add/OK buttons.
  272.             document.getElementById("simtim-btnAddOk").label =
  273.                     strbundle.getString("button.label.add/OK");
  274.             document.getElementById("simtim-btnAddOk").tooltipText =
  275.                         strbundle.getString("button.tooltip.add/OK");
  276.             document.getElementById("simtim-btnAdd").label =
  277.                     strbundle.getString("button.label.add");
  278.             document.getElementById("simtim-btnAdd").tooltipText =
  279.                         strbundle.getString("button.tooltip.add");
  280.         }
  281.  
  282.         // If URL present, check for valid protocol.
  283.         var url = document.getElementById("simtim-tboxNotifyUrl").value;
  284.  
  285.         if ( url ) {
  286.             // Strip whitespace.
  287.             newUrl = url.replace(/[\n\t ]+/, "");
  288.             if ( this.validateUrl(newUrl) ) {
  289.                 document.getElementById("simtim-tboxNotifyUrl").value = newUrl;
  290.                 url = newUrl;
  291.             }
  292.             else {
  293.                 alert(strbundle.getString("alert.error.invalid.url"));
  294.                 return false;
  295.             }
  296.         }
  297.  
  298.         if ( recurring ) {
  299.             // Default dow is daily.
  300.             var mlistDow = document.getElementById("simtim-mlistDow");
  301.             var dowSelected = mlistDow.selectedItem;
  302.             dowValue = dowSelected ?
  303.                     dowSelected.value : "7";  // daily = 7
  304.         }
  305.         else {
  306.             dowValue = "-1";
  307.         }
  308.  
  309.         // Check if the new/updated notification time is a duplicate.
  310.         if ( treeChildren.hasChildNodes() ) {
  311.             var dup = false;
  312.             treeItems = treeChildren.childNodes;
  313.             tree.view.selection.clearSelection();
  314.             var len = treeItems.length;
  315.  
  316.             for ( var i = 0; i < len; i++ ) {
  317.                 row = treeItems[i].firstChild;
  318.                 timeNode = row.firstChild;
  319.                 dayNode = timeNode.nextSibling.nextSibling;
  320.  
  321.                 if ( timeDisplay ===  timeNode.getAttribute("label") &&
  322.                      parseInt(updaterow, 10) !== i ) {
  323.                     // Time match.
  324.                     if ( dowValue === dayNode.getAttribute("value") ||
  325.                          this.checkDuplicateDay(parseInt(dayNode.getAttribute("value"), 10),
  326.                                                 parseInt(dowValue, 10))) {
  327.                         // dow match or conflict.
  328.                         // Maybe more than one dup, keep searching and highlight them all.
  329.                         // Parm "true" indicates add to current selection.
  330.                         tree.view.selection.rangedSelect(i, i, true);
  331.                         dup = true;
  332.                     }
  333.                 }
  334.             }
  335.  
  336.             if ( dup ) {
  337.                 // Notify user the not. was added, but with conflict(s).
  338.                 // Alerts will occur about 3 seconds apart.
  339.                 updaterow ? alert(strbundle.getString("alert.warning.duplicate.notification.upd")) :
  340.                             alert(strbundle.getString("alert.warning.duplicate.notification.add"));
  341.             }
  342.         }
  343.  
  344.         var data = {completed: false, recurring: recurring, timeDisplay: timeDisplay,
  345.                 description: descr, time24hr: time24hr, timeMilli: 0,
  346.                 day: dowValue, url: url};
  347.  
  348.         this.addTreeRow(treeChildren, data);
  349.  
  350.         // Select the added/updated row. Add to any existing selections (duplicates).
  351.         var rowIndex;
  352.  
  353.         if ( updaterow ) {
  354.             rowIndex = parseInt(updaterow, 10);
  355.         }
  356.         else if ( treeItems ) {
  357.             rowIndex = treeItems.length - 1;
  358.         }
  359.         else {
  360.             // Should never reach this.
  361.             rowIndex = 0;
  362.         }
  363.  
  364.         tree.view.selection.rangedSelect(rowIndex, rowIndex, true);
  365.  
  366.         // Ensure the added/updated row is visible, tree displays 5 rows (unless dlg stretched).
  367.         if ( treeItems && treeItems.length > 5 ) {
  368.             boxobject.ensureRowIsVisible(rowIndex);
  369.         }
  370.  
  371.         // Clear the textboxes.
  372.         document.getElementById("simtim-tboxNotifyDesc").value = "";
  373.         document.getElementById("simtim-tboxNotifyUrl").value = "";
  374.  
  375.         return true;
  376.     },
  377.  
  378.     // Add a notification to the tree.
  379.  
  380.     addTreeRow: function(treeChildren, data) {
  381.         var item = document.createElement("treeitem");
  382.         var row = document.createElement("treerow");
  383.         var cell;
  384.  
  385.         // First cell is "timeDisplay", not editable. Stash "time24hr" in value.
  386.         cell = document.createElement("treecell");
  387.         cell.setAttribute("label", data.timeDisplay);
  388.         cell.setAttribute("value", data.time24hr);
  389.         cell.setAttribute("editable", "false");
  390.         row.appendChild(cell);
  391.  
  392.         // Second cell is "recurring", displayed as a checkbox.
  393.         cell = document.createElement("treecell");
  394.         cell.setAttribute("value", data.recurring);
  395.         row.appendChild(cell);
  396.  
  397.         // Third cell is "day". Stash numeric value of day in value.
  398.         cell = document.createElement("treecell");
  399.         cell.setAttribute("label", this.getDow(parseInt(data.day, 10)));
  400.         cell.setAttribute("value", data.day);
  401.         cell.setAttribute("editable", "false");
  402.         row.appendChild(cell);
  403.  
  404.         // Fourth cell is "URL", the optional URL".
  405.         cell = document.createElement("treecell");
  406.         cell.setAttribute("label", data.url);
  407.         cell.setAttribute("editable", "false");
  408.         row.appendChild(cell);
  409.  
  410.         // Fifth cell is "descr", the optional description. Stash "completed".
  411.         cell = document.createElement("treecell");
  412.         cell.setAttribute("label", data.description);
  413.         cell.setAttribute("value", data.completed);
  414.         cell.setAttribute("editable", "false");
  415.         row.appendChild(cell);
  416.  
  417.         // Add the treerow onto treeitem.
  418.         item.appendChild(row);
  419.  
  420.         // Append or replace? Check our custom attrribute.
  421.         var updaterow = document.getElementById("simtim-tboxNotifyDesc").
  422.                 getAttribute("updaterow");
  423.  
  424.         if ( updaterow ) {
  425.             treeChildren.replaceChild(item, treeChildren.childNodes[parseInt(updaterow, 10)]);
  426.             document.getElementById("simtim-tboxNotifyDesc").setAttribute("updaterow", "");
  427.         }
  428.         else {
  429.             treeChildren.appendChild(item);
  430.         }
  431.     },
  432.  
  433.     // Called when Delete button is clicked.
  434.  
  435.     onDeleteNotify: function() {
  436.         var strbundle = document.getElementById("simtim-strings");
  437.         var tree = document.getElementById("simtim-treeNotifications");
  438.  
  439.         if ( tree.currentIndex < 0 ) {
  440.             // User didn't make a selection.
  441.             alert(strbundle.getString("alert.warning.no.item.selected"));
  442.         }
  443.         else {
  444.             var start = {};
  445.             var end = {};
  446.             var treeItem;
  447.             var numRanges = tree.view.selection.getRangeCount();
  448.  
  449.             // Start with the last selection range and work up.
  450.             for ( var i = numRanges - 1; i >= 0; i-- ) {
  451.                 tree.view.selection.getRangeAt(i,start,end);
  452.  
  453.                 // Within a range, delete from the bottom up.
  454.                 for ( var j = end.value; j >= start.value; j-- ) {
  455.                     treeItem = tree.view.getItemAtIndex(j);
  456.                     treeItem.parentNode.removeChild(treeItem);
  457.                 }
  458.             }
  459.         }
  460.     },
  461.  
  462.     // Called when Del All button is clicked.
  463.  
  464.     onDeleteAllNotify: function() {
  465.         var treeChildren = document.getElementById("simtim-treeItems");
  466.  
  467.         // Remove from the bottom.
  468.         while ( treeChildren.hasChildNodes() ) {
  469.             treeChildren.removeChild(treeChildren.lastChild);
  470.         }
  471.     },
  472.  
  473.     // Check if newly added/updated not. will result in alarms
  474.     // occuring at the same time.
  475.  
  476.     checkDuplicateDay: function(oldDow, newDow) {
  477.         // Not. day values:
  478.         // 0-6 for Sun-Sat,
  479.         // 7 daily, 8 weekdays, 9 weekends.
  480.         // -1 for non-recurring (ignore them).
  481.         if ( oldDow === 7 || newDow === 7 ||
  482.              (( oldDow === 0 || oldDow === 6 ) && newDow === 9) ||
  483.              (( newDow === 0 || newDow === 6 ) && oldDow === 9) ||
  484.              (( oldDow > 0 && oldDow < 6 ) && newDow === 8) ||
  485.              (( newDow > 0 && newDow < 6 ) && oldDow === 8) ) {
  486.             return true;
  487.         }
  488.         else {
  489.             return false;
  490.         }
  491.     },
  492.  
  493.     // Convert numeric value to day of week.
  494.  
  495.     getDow: function(dow) {
  496.         var strbundle = document.getElementById("simtim-strings");
  497.         var daysOfWeek = [
  498.                 strbundle.getString("display.dow.short.sunday"),
  499.                 strbundle.getString("display.dow.short.monday"),
  500.                 strbundle.getString("display.dow.short.tuesday"),
  501.                 strbundle.getString("display.dow.short.wednesday"),
  502.                 strbundle.getString("display.dow.short.thursday"),
  503.                 strbundle.getString("display.dow.short.friday"),
  504.                 strbundle.getString("display.dow.short.saturday"),
  505.                 strbundle.getString("display.dow.daily"),
  506.                 strbundle.getString("display.dow.short.weekdays"),
  507.                 strbundle.getString("display.dow.short.weekend")
  508.                 ];
  509.  
  510.         if ( daysOfWeek[dow] != null ) {
  511.             // When undefined (ie. daysOfWeek[-1]) is loosely compared to null, the result is true.
  512.             return daysOfWeek[dow];
  513.         }
  514.         else if ( dow === -1 ) {
  515.             // Non-recurring.
  516.             return "";
  517.         }
  518.         else {
  519.             alert(strbundle.getString("alert.error.invalid.dow"));
  520.             return "Err";
  521.         }
  522.     },
  523.  
  524.     // Called when user adds a notification containing a URL.
  525.  
  526.     validateUrl: function(url) {
  527.         // Check for supported protocols.
  528.         if ( url.match(/^http:\/\//) ||
  529.              url.match(/^https:\/\//) ||
  530.              url.match(/^file:\/\/\//) ||
  531.              url.match(/^ftp:\/\//) ||
  532.              url.match(/^chrome:\/\//) ) {
  533.             return true;
  534.         }
  535.         else {
  536.             return false;
  537.         }
  538.     },
  539.  
  540.     // Convert notification time to milli.
  541.  
  542.     calcNotTimeMilli: function(time24hr) {
  543.         var notTime = time24hr.split(":");
  544.         var currDate = new Date();
  545.         var day = currDate.getDate();
  546.  
  547.         // Note the use of a radix in parseInt, otherwise
  548.         // leading 0 values get treated as octal. (eg "08" "09" return 0)
  549.         var notDate = new Date(currDate.getFullYear(), currDate.getMonth(),
  550.                 day, parseInt(notTime[0], 10), parseInt(notTime[1], 10));
  551.  
  552.         if ( currDate.getTime() >= notDate.getTime() ){
  553.             // Also adjusts for dst if necessary.
  554.             notDate.setDate(day + 1);
  555.         }
  556.  
  557.         return notDate.getTime();
  558.     },
  559.  
  560.     // Called when user clicks OK.
  561.  
  562.     onOKNotify: function() {
  563.         var prefs = Components.classes["@mozilla.org/preferences-service;1"].
  564.                 getService(Components.interfaces.nsIPrefService).
  565.                 getBranch("extensions.simpletimer@grbradt.org.");
  566.  
  567.         this.buildNotifyTable();
  568.         this.updateNotifyPref();
  569.  
  570.         // Trigger pref observer, which starts not. processing.
  571.         prefs.setBoolPref("notifyClosed", !prefs.getBoolPref("notifyClosed"));
  572.     },
  573.  
  574.     // Construct an array of notification objects.
  575.     // Called when the user clicks OK or Add/OK.
  576.  
  577.     buildNotifyTable: function() {
  578.         var Application = Components.classes["@mozilla.org/fuel/application;1"].
  579.                 getService(Components.interfaces.fuelIApplication);
  580.  
  581.         var treeChildren = document.getElementById("simtim-treeItems");
  582.         var notifyTable = [];
  583.  
  584.         if ( treeChildren.hasChildNodes() ) {
  585.             // Build table of all nots. from dialog not. tree.
  586.             var row, timeNode, timeDisplay, time24hr, timeMilli, recurring, dayNode,
  587.                 day, url, description, completed;
  588.  
  589.             var treeItems = treeChildren.childNodes;
  590.             var len = treeItems.length;
  591.  
  592.             for ( var i = 0; i < len; i++ ) {
  593.                 row = treeItems[i].firstChild;
  594.                 timeNode = row.firstChild;
  595.                 timeDisplay = timeNode.getAttribute("label");
  596.  
  597.                 // Zero pad the hours if necessary.
  598.                 time24hr = timeNode.getAttribute("value");
  599.                 time24hr = ( time24hr.indexOf(":") === 1 ) ?
  600.                         "0" + time24hr : time24hr;
  601.  
  602.                 timeMilli = this.calcNotTimeMilli(time24hr);
  603.  
  604.                 // Convert string attribute to boolean.
  605.                 recurring = ( row.firstChild.nextSibling.
  606.                                 getAttribute("value") === "true" ) ?
  607.                         true : false;
  608.  
  609.                 dayNode = row.firstChild.nextSibling.nextSibling;
  610.                 day = parseInt(dayNode.getAttribute("value"), 10);
  611.  
  612.                 url = row.lastChild.previousSibling.getAttribute("label");
  613.  
  614.                 description = row.lastChild.getAttribute("label");
  615.                 completed = ( row.lastChild.getAttribute("value") === "true" ) ?
  616.                         true : false;
  617.  
  618.                 notifyTable[i] = {completed: completed,
  619.                                   recurring: recurring,
  620.                                   timeDisplay: timeDisplay,
  621.                                   description: description,
  622.                                   time24hr: time24hr,
  623.                                   timeMilli: timeMilli,
  624.                                   day: day,
  625.                                   url: url
  626.                                  };
  627.             }
  628.         }
  629.  
  630.         // Sort the table on time24hr property.
  631.         if ( notifyTable.length > 1 ) {
  632.             notifyTable.sort(this.sortNotifyTable);
  633.         }
  634.  
  635.         Application.storage.set("notifyTable", notifyTable);
  636.     },
  637.  
  638.     // Construct an array of notification objects from the notifyRecur pref,
  639.     // and store via FUEL. Called at startup.
  640.  
  641.     buildNotifyTableFromPref: function(notifyRecur) {
  642.         var strbundle = document.getElementById("simtim-strings");
  643.  
  644.         // Objects stored as JSON strings in the pref.
  645.         var json = Components.classes["@mozilla.org/dom/json;1"].
  646.                 createInstance(Components.interfaces.nsIJSON);
  647.  
  648.         var notExpired = false;
  649.         var description;
  650.         var eventTime, eventDescription, eventUrl;
  651.         var expiredArray = [];
  652.         var notifyTable = [];
  653.         var notifyArray = notifyRecur.split(this.notifySep);
  654.         var currTimeMilli = new Date().getTime();
  655.  
  656.         for ( var i in notifyArray ) {
  657.             notifyTable[i] = json.decode(notifyArray[i]);
  658.  
  659.             // Check if a non-recurring not. expired while user
  660.             // was logged off, if so mark completed.
  661.             if ( !notifyTable[i].recurring &&
  662.                  !notifyTable[i].completed &&
  663.                   currTimeMilli > notifyTable[i].timeMilli ) {
  664.                 notExpired = true;
  665.                 notifyTable[i].completed = true;
  666.  
  667.                 expiredArray.push({timeDisplay: notifyTable[i].timeDisplay,
  668.                                    description: notifyTable[i].description});
  669.  
  670.                 if ( SimpleTimer.eventLogging ) {
  671.                     eventTime = notifyTable[i].timeDisplay;
  672.                     eventDescription = ( notifyTable[i].description ) ?
  673.                             notifyTable[i].description :
  674.                             strbundle.getString("msg.none");
  675.                     eventUrl = ( notifyTable[i].url ) ?
  676.                             notifyTable[i].url :
  677.                             strbundle.getString("msg.none");
  678.  
  679.                     // Pass event type, event time, recurring, status, description, URL.
  680.                     SimpleTimerEventLog.logEvent(strbundle.getString("msg.notification"),
  681.                                                  eventTime,
  682.                                                  strbundle.getString("msg.no"),
  683.                                                  strbundle.getString("msg.expired"),
  684.                                                  eventDescription,
  685.                                                  eventUrl);
  686.                 }
  687.             }
  688.  
  689.             if ( !notifyTable[i].completed ) {
  690.                 notifyTable[i].timeMilli = this.calcNotTimeMilli(notifyTable[i].time24hr);
  691.             }
  692.         }
  693.  
  694.         // Sort the table on time24hr property.
  695.         if ( notifyTable.length > 1 ) {
  696.             notifyTable.sort(this.sortNotifyTable);
  697.         }
  698.  
  699.         Application.storage.set("notifyTable", notifyTable);
  700.  
  701.         if ( notExpired ) {
  702.             this.updateNotifyPref();
  703.  
  704.             if ( !SimpleTimer.otherBrowserWindowOpen() ) {
  705.                 var params = { displayItems: expiredArray, msg: strbundle.getString("msg.notify.lost")};
  706.                 setTimeout(SimpleTimerSliderAlert.addMessageToQueue, 5000, params);
  707. //                setTimeout("SimpleTimer.displayPopup('lostNotify')", 5000);
  708.                 setTimeout("SimpleTimer.playSound(3)", 5000);
  709.             }
  710.         }
  711.     },
  712.  
  713.     // Sort the notification table on objects time24hr property.
  714.  
  715.     sortNotifyTable: function(a, b) {
  716.         var x = a.time24hr;
  717.         var y = b.time24hr;
  718.  
  719.         return ( (x < y) ? -1 : ( (x > y) ? 1 : 0 ) );
  720.     },
  721.  
  722.     // Convert notifications to JSON strings and store in notifyRecur pref.
  723.     // The name "notifyRecur" is now a misnomer since all nots. are retained
  724.     // between sessions, it is a legacy from earlier versions where only
  725.     // recurring nots. were retained.
  726.  
  727.     updateNotifyPref: function() {
  728.         var prefs = Components.classes["@mozilla.org/preferences-service;1"].
  729.                 getService(Components.interfaces.nsIPrefService).
  730.                 getBranch("extensions.simpletimer@grbradt.org.");
  731.  
  732.         // For Unicode.
  733.         var str = Components.classes["@mozilla.org/supports-string;1"].
  734.                 createInstance(Components.interfaces.nsISupportsString);
  735.  
  736.         var Application = Components.classes["@mozilla.org/fuel/application;1"].
  737.                 getService(Components.interfaces.fuelIApplication);
  738.  
  739.         var notifyTable = [];
  740.  
  741.         if ( Application.storage.has("notifyTable") ) {
  742.             notifyTable = Application.storage.get("notifyTable", "ERR");
  743.  
  744.             if ( notifyTable === "ERR" ) {
  745.                 alert(strbundle.getString("alert.error.loading.notifications"));
  746.                 return;
  747.             }
  748.         }
  749.         else {
  750.             alert(strbundle.getString("alert.error.loading.notifications"));
  751.             return;
  752.         }
  753.  
  754.         // Objects stored as JSON strings in the pref.
  755.         var json = Components.classes["@mozilla.org/dom/json;1"].
  756.                 createInstance(Components.interfaces.nsIJSON);
  757.  
  758.         var prefString = "";
  759.  
  760.         for ( var i in notifyTable ) {
  761.             prefString += json.encode(notifyTable[i]) + this.notifySep;
  762.         }
  763.  
  764.         // Remove last separator.
  765.         if ( prefString ) {
  766.             prefString = prefString.substring(0, prefString.length - 1);
  767.         }
  768.  
  769.         str.data = prefString;
  770.         prefs.setComplexValue("notifyRecur",
  771.                 Components.interfaces.nsISupportsString, str);
  772.     },
  773.  
  774.     // Open page in new tab.
  775.     // This function developed by Devon Jensen, from Download Statusbar.
  776.  
  777.     openLink: function(aPage) {
  778.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
  779.                 getService();
  780.         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  781.         var win = wmed.getMostRecentWindow("navigator:browser");
  782.  
  783.         if ( !win ) {
  784.             win = window.openDialog(
  785.                     "chrome://browser/content/browser.xul",
  786.                     "_blank",
  787.                     "chrome, all, dialog=no",
  788.                     aPage, null, null);
  789.         }
  790.         else {
  791.             var content = win.document.getElementById("content");
  792.             content.selectedTab = content.addTab(aPage);
  793.         }
  794.     },
  795.  
  796.     // Dump to console.
  797.  
  798.     dumpNotificationObject: function (notification) {
  799.         this.debug("\nNotification data:");
  800.         this.debug("Completed?: " + notification.completed);
  801.         this.debug("Recurring?: " + notification.recurring);
  802.         this.debug("Notification time: " + notification.timeDisplay);
  803.         this.debug("Description: " + notification.description);
  804.         this.debug("Notification time (24 hr format): " + notification.time24hr);
  805.         this.debug("Notification time (millisecs): " + notification.timeMilli);
  806.         this.debug("Notification day: " + notification.day);
  807.         this.debug("Url: " + notification.url);
  808.     },
  809.  
  810.     // Debug messages to console.
  811.  
  812.     debug: function (aMsg) {
  813.         setTimeout(function() { throw new Error("[debug] " + aMsg); }, 0);
  814.     }
  815. };
  816.